﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using OpenMcdf;
using System.Windows.Forms;

namespace Word.Editor.Doc
{
    public class DocFileStream 
    {
        TreeView structureTV = null;
        TreeNode wordStreamTN = null;
        TreeNode FibTN = null;
        TreeNode FibRgFcLcbTN = null;
        TreeNode tableStreamTN = null;
        TreeNode root = null;

        private CompoundFile compoundFile = null;
        public FileStream fileStream = null;

        private MemoryStream _wordDocumentStream = null;
        private MemoryStream _tableStream = null;

        public Fib _sFib;
        /// <summary>
        /// An unsigned integer that specifies the count of 16-bit values corresponding to fibRgCswNew that follow. 
        /// This MUST be one of the following values, depending on the value of nFib.
        /// </summary>
        public UInt16 _vcswNew;
        /// <summary>
        /// An unsigned integer that specifies the version number of the file format that is used. This value MUST be one of the following.
        /// </summary>
        public UInt16 VersionNumberFileFormat = 0;
        public FibRgCswNewData2007 rgCswNewData;

        #region fibRgFcLcb
        public FibRgFcLcb97 _sfibRgFcLcb;
        #endregion

        private Clx _sClx = null;
        private PlcBtePapx _sPlcBtePapx = null;
        private PlcBteChpx _sPlcBteChpx = null;
        private ChpxFkp[] _stChpxFkp = null;
        
        public String msg;

        public DocFileStream(TreeView _tv)
        {
            structureTV = _tv;
        }


        public void Open(String path,FileMode fileMode)
        {
            msg = String.Empty;
            fileStream = new FileStream(path, fileMode);
            compoundFile = new CompoundFile(fileStream);
            RefreshTree(compoundFile);
           

            CFStream  cfs = compoundFile.RootStorage.GetStream(Strings.WordStreamName);
            _wordDocumentStream = new MemoryStream(cfs.GetData());

            


            ReadFibStructure(_wordDocumentStream);
            TreeNode PlcBtePapxTN = null, PlcBteChpxTN=null;
            if (tableStreamTN != null)
            {
                PlcBteChpxTN = tableStreamTN.Nodes.Add("PlcBteChpx", string.Format("PlcBteChpx->{0:X}", _sfibRgFcLcb.fcPlcfBteChpx));
                PlcBtePapxTN = tableStreamTN.Nodes.Add("PlcBtePapx", string.Format("PlcBtePapx->{0:X}", _sfibRgFcLcb.fcPlcfBtePapx));
            }

            _sPlcBtePapx = ReadInternalStructure<PlcBtePapx>(_tableStream, _sfibRgFcLcb.fcPlcfBtePapx, (Int32)_sfibRgFcLcb.lcbPlcfBtePapx, PlcBtePapxTN);
            _sPlcBtePapx.AddToTree(PlcBtePapxTN);

            _sPlcBteChpx = ReadInternalStructure<PlcBteChpx>(_tableStream, _sfibRgFcLcb.fcPlcfBteChpx, (Int32)_sfibRgFcLcb.lcbPlcfBteChpx, PlcBteChpxTN);
            _sPlcBteChpx.AddToTree(PlcBteChpxTN);
            TreeNode ChpxFkpTN = null;
            if (_wordDocumentStream != null)
            {
                
            }
            

            TreeNode clxTN = null;
            if (tableStreamTN!=null)
                clxTN=tableStreamTN.Nodes.Add("Clx", string.Format("Clx->{0:X}", _sfibRgFcLcb.fcClx));
            _sClx = ReadInternalStructure<Clx>(_tableStream, _sfibRgFcLcb.fcClx, (Int32)_sfibRgFcLcb.lcbClx);
            _sClx.AddToTree(clxTN);
            msg += GetFileTextContent(cfs.GetData());

            _stChpxFkp = new ChpxFkp[_sPlcBteChpx.DataNo];
            for(int i=0; i<_sPlcBteChpx.DataNo;i++)
            {
                if (_wordDocumentStream != null)
                {
                    if (wordStreamTN != null)
                        ChpxFkpTN = wordStreamTN.Nodes.Add("ChpxFkp" + i.ToString(), string.Format("ChpxFkp({0})->{1:X}", i, _sPlcBteChpx.aPnBteChpx[i] * 512));
                }
                _stChpxFkp[i] = ReadInternalStructure<ChpxFkp>(_wordDocumentStream, _sPlcBteChpx.aPnBteChpx[i] * 512, 512);
                _stChpxFkp[i].AddToTree(ChpxFkpTN);
                //ReadAllChpxStructure(_stChpxFkp[i], _tableStream, (int)_sPlcBteChpx.aPnBteChpx[i] * 512, tableStreamTN);
            }
            
            
        }

        private void RefreshTree(CompoundFile cf)
        {
            structureTV.Nodes.Clear();
            root = structureTV.Nodes.Add("Root Entry", "Root");
            root.ImageIndex = 0;
            root.Tag = cf.RootStorage;

            //Recursive function to get all storage and streams
            AddNodes(root, cf.RootStorage);

            wordStreamTN = root.Nodes.Find(Strings.WordStreamName,true)[0];
            root.ExpandAll();
        }

        /// <summary>
        /// Recursive addition of tree nodes foreach child of current item in the storage
        /// </summary>
        /// <param name="node">Current TreeNode</param>
        /// <param name="cfs">Current storage associated with node</param>
        private void AddNodes(TreeNode node, CFStorage cfs)
        {
            VisitedEntryAction va = delegate(CFItem target)
            {
                TreeNode temp = node.Nodes.Add(
                    target.Name,
                    target.Name + (target.IsStream ? " (" + target.Size + " bytes )" : "")
                    );

                temp.Tag = target;

                if (target.IsStream)
                {
                    //Stream
                    temp.ImageIndex = 1;
                    temp.SelectedImageIndex = 1;

                }
                else
                {
                    //Storage
                    temp.ImageIndex = 0;
                    temp.SelectedImageIndex = 0;

                    //Recursion into the storage
                    AddNodes(temp, (CFStorage)target);
                }
            };

            //Visit NON-recursively (first level only)
            cfs.VisitEntries(va, false);
        }

        private String GetFileTextContent(byte[] _bstream)
        {
            if (_sClx == null)
                throw new NullReferenceException(Strings.ClxNullReference);
            String ret = String.Empty;
            int lg = 0;
            int start = 0;
            
            for (int i = 0; i < _sClx.pcdt.aCP.Length - 1; i++)
            {
                lg = (int)(_sClx.pcdt.aCP[i + 1]  - _sClx.pcdt.aCP[i]);
                if (_sClx.pcdt.aPcd[i].WDSTextStart > _wordDocumentStream.Length)
                    throw new OutOfMemoryException(Strings.WDSTextStart_OutOfBounds);
                start = (int)_sClx.pcdt.aPcd[i].WDSTextStart;
                //ret = Encoding.ASCII
                if (_sClx.pcdt.aPcd[i].compressed)
                    ret += Encoding.ASCII.GetString(_bstream, start, lg);
                else
                   ret += Encoding.Unicode.GetString(_bstream, start, lg*2);

            }
            return ret;
        }

        private void ReadFibStructure(MemoryStream wordDocBffer)
        {
            if (wordStreamTN != null)
                FibTN = wordStreamTN.Nodes.Add("Fib",string.Format("Fib->{0:d} ", wordDocBffer.Position));
            _sFib = ReadStruct<Fib>(wordDocBffer);

            FibRgFcLcbTN = FibTN.Nodes.Add("Fib.FibRgLw97", string.Format("FibRgLw97"));
            FibRgFcLcbTN.Nodes.Add("Fib.FibRgLw97.ccpText", string.Format("ccpText={0:X}", _sFib.fibRgLw.ccpText));
            VersionNumberFileFormat = _sFib.fibBase.nFib;

            
            if (_sFib.csw != 0x000E)
                throw new InvalidDataException(Strings.Fib_csw_invalid_value);
            if (_sFib.cslw != 0x0016)
                throw new InvalidDataException(Strings.Fib_cslw_invalid_value);
            
            switch (_sFib.cbRgFcLcb)
            {
                case 0x005D:
                    FibRgFcLcbTN = FibTN.Nodes.Add("Fib.FibRgFcLcb97",string.Format("FibRgFcLcb97->{0:X}", wordDocBffer.Position));
                    _sfibRgFcLcb = ReadStruct<FibRgFcLcb97>(wordDocBffer);
                    break;
                case 0x006C:
                    FibRgFcLcbTN = FibTN.Nodes.Add("Fib.FibRgFcLcb2000",string.Format("FibRgFcLcb2000->{0:X}", wordDocBffer.Position));
                    _sfibRgFcLcb = ReadStruct<FibRgFcLcb2000>(wordDocBffer);
                    break;
                case 0x0088:
                    FibRgFcLcbTN = FibTN.Nodes.Add("Fib.FibRgFcLcb2002",string.Format("FibRgFcLcb2002->{0:X}", wordDocBffer.Position));
                    _sfibRgFcLcb = ReadStruct<FibRgFcLcb2002>(wordDocBffer);
                    break;
                case 0x00A4:
                    FibRgFcLcbTN = FibTN.Nodes.Add("Fib.FibRgFcLcb2003",string.Format("FibRgFcLcb2003->{0:X}", wordDocBffer.Position));
                    _sfibRgFcLcb = ReadStruct<FibRgFcLcb2003>(wordDocBffer);
                    break;
                case 0x00B7:
                    FibRgFcLcbTN = FibTN.Nodes.Add("Fib.FibRgFcLcb2007",string.Format("FibRgFcLcb2007->{0:X}", wordDocBffer.Position));
                    _sfibRgFcLcb = ReadStruct<FibRgFcLcb2007>(wordDocBffer);
                    break;
                default:
                    throw new InvalidDataException(Strings.FibRgFcLcb_InvalidStructure);
            }
            if (_sfibRgFcLcb.lcbClx <= 0)
                throw new InvalidDataException(Strings.FibRgFcLcb_IncorectlcbClxValue);


            if (FibRgFcLcbTN != null)
            {
                FibRgFcLcbTN.Nodes.Add("Fib.fibRgFcLcb.fcPlcfBteChpx", string.Format("fcPlcfBteChpx={0,-8:X}", _sfibRgFcLcb.fcPlcfBteChpx));
                FibRgFcLcbTN.Nodes.Add("Fib.fibRgFcLcb.lcbPlcfBteChpx", string.Format("lcbPlcfBteChpx={0,-8:X}", _sfibRgFcLcb.lcbPlcfBteChpx));
                FibRgFcLcbTN.Nodes.Add("Fib.fibRgFcLcb.fcPlcBtePapx", string.Format("fcPlcBtePapx={0:X}", _sfibRgFcLcb.fcPlcfBtePapx));
                FibRgFcLcbTN.Nodes.Add("Fib.fibRgFcLcb.lcbPlcfBtePapx", string.Format("lcbPlcfBtePapx={0:X}", _sfibRgFcLcb.lcbPlcfBtePapx));
                FibRgFcLcbTN.Nodes.Add("Fib.fibRgFcLcb.fcClx", string.Format("fcClx={0:X}", _sfibRgFcLcb.fcClx));
                FibRgFcLcbTN.Nodes.Add("Fib.fibRgFcLcb.lcbClx", string.Format("lcbClx={0:X}", _sfibRgFcLcb.lcbClx));
            }

            _vcswNew = ReadUInt16(wordDocBffer);
            if (FibTN != null)
                FibTN.Nodes.Add("Fib.csNew", string.Format("csNew={0:X}", _vcswNew));

            if (_vcswNew != 0 && _vcswNew != 2 && _vcswNew!=5)
                throw new InvalidDataException(Strings.cswNew_InvalidValue);

            if (_vcswNew != 0)
            {
                VersionNumberFileFormat = ReadUInt16(wordDocBffer);
                switch (VersionNumberFileFormat)
                {
                    case 0x00D9:
                    case 0x0101:
                    case 0x010C:
                        rgCswNewData.cQuickSavesNew = ReadUInt16(wordDocBffer);
                        break;
                    case 0x0112:
                        rgCswNewData = ReadStruct<FibRgCswNewData2007>(wordDocBffer);
                        break;
                    default:
                        throw new InvalidDataException(Strings.VersionNumberFileFormat_InvalidValue);
                }
            }
            
            if (FibTN != null)
                FibTN.Nodes.Add("nVersionNumberFileFormat", string.Format("nVersionNumberFileFormat={0:X}", VersionNumberFileFormat));

            String TableName = "0Table";
            if (GetFibGroupValue(_sFib.fibBase,'G')==1)
                TableName = "1Table";

            tableStreamTN = root.Nodes.Find(TableName,true)[0];
            CFStream cfs = compoundFile.RootStorage.GetStream(TableName);
            _tableStream = new MemoryStream(cfs.GetData());
        }

        private void ReadAllChpxStructure(ChpxFkp _fkp, MemoryStream stream, int offset,TreeNode tn)
        {
            if (stream == null)
                return;
            _fkp._chpx = new Chpx[_fkp.crun];
            for (int i = 0; i < _fkp.crun; i++)
            {
                _fkp._chpx[i] = new Chpx();
                _tableStream.Position = offset + _fkp.rgb[i]* 2;
                TreeNode tnchpx = tn.Nodes.Add("Chpx." + i.ToString(), string.Format("Chpx[{0}]->{1:X} + {2:X}", i, offset, _fkp.rgb[i] * 2));
                _fkp._chpx[i].cb = (byte)stream.ReadByte();
                tnchpx.Nodes.Add("Chpx." + i.ToString() + ".cb", string.Format("cb = {0}", _fkp._chpx[i].cb));
                byte[] prlListBuffer = new byte[_fkp._chpx[i].cb];
                //_tableStream.Position = offset + _fkp.rgb[i] * 2 +1;
                stream.Read(prlListBuffer, 0, _fkp._chpx[i].cb);
                tnchpx.Nodes.Add("Chpx." + i.ToString() + ".prl", DisplayHexaValuesOneRow(prlListBuffer));
                ReadChpxStructure(_fkp._chpx[i], prlListBuffer, tnchpx);

            }
        }

        private void ReadChpxStructure(Chpx _vchpx, byte[] buffer, TreeNode tn)
        {
            List<Prl> tmpList = new List<Prl>();
            int prlByteRead = 0;
            TreeNode tmpNode = tn.Nodes.Add("Chpx.prl", string.Format("grpprl"));
            int i = 0;
            while (prlByteRead < buffer.Length)
            {
                Prl tmp = new Prl(PLC.GetUint16(buffer, prlByteRead));
                prlByteRead += 2;
                prlByteRead += tmp.sprm.lgOperand;
                tmpList.Add(tmp);
                tmpNode.Nodes.Add("Chpx.prl" + tmpList.Count.ToString(), string.Format("[{0}] - sgc={1}", i, tmp.sprm.sgc));
                if (tmp.sprm.lgOperand == 100)
                    break;
            }
            _vchpx.grpprl = tmpList.ToArray();
            
        }

        private T ReadInternalStructure<T>(Stream stream, UInt32 offset, Int32 SizeInBytes, TreeNode tn=null) where T:IPLC, new()
        {
            T val = new T();
            if (offset >= stream.Length)
                throw new OutOfMemoryException(String.Format(Strings.ReadStructure_WrongOffset, val.GetType().ToString()));
            if (offset + SizeInBytes > stream.Length)
                throw new OutOfMemoryException(String.Format(Strings.ReadStructure_WrongSizeInBytes, val.GetType().ToString()));

            byte[] ret = new byte[SizeInBytes];
            stream.Position = offset;
            stream.Read(ret, 0, SizeInBytes);
            if (tn != null)
                tn.Text += " ["+ DisplayHexaValuesOneRow(ret) + "]";
            val.ParseBuffer(ret);
            return val;
        }

        public void Close()
        {
            if(fileStream!=null)
                fileStream.Close();
        }

        public byte[] ConvertToLittleEndian(byte[] buffer)
        {
            byte[] ret = new byte[((buffer.Length/4) +1)*4];
            for(int i=0;i<(buffer.Length/4 +1);i++)
            {
                ret[4 * i] = (4 * i + 3 < buffer.Length) ? (buffer[4 * i + 3]) : (byte)0;
                ret[4 * i + 1] = (4 * i + 2 < buffer.Length) ? (buffer[4 * i + 2]) : (byte)0;
                ret[4 * i + 2] = (4 * i + 1 < buffer.Length) ? (buffer[4 * i + 1]) : (byte)0;
                ret[4 * i + 3] = (4 * i < buffer.Length) ? (buffer[4 * i]) : (byte)0;
            }
            return ret;
        }

        public T ReadStruct<T>(Stream stream)
        {
            var sz = Marshal.SizeOf(typeof(T));
            var buffer = new byte[sz];
            stream.Read(buffer, 0, sz);
            //buffer = ConvertToLittleEndian(buffer);
            var pinnedBuffer = GCHandle.Alloc(buffer, GCHandleType.Pinned);
            var structure = (T)Marshal.PtrToStructure(pinnedBuffer.AddrOfPinnedObject(), typeof(T));
            pinnedBuffer.Free();
            return structure;
        }

        public UInt16 ReadUInt16(Stream stream)
        {
            byte[] ret = new byte[2];
            stream.Read(ret, 0, 2);

            UInt16 ret1 = (UInt16)((ret[1] << 8) | ret[0]);
            return ret1;
        }

        public String DisplayHexaValuesOneRow(byte[] Buffer)
        {
            String hexa = "";
            //var Buffer = new byte[fileStream.Length];
            //fileStream.Read(Buffer, 0, (int)fileStream.Length);
            for (int i = 0; i < Buffer.Length; i++)
            {
                if (i != 0 && (i % 4 == 0)) hexa += " | ";
                //if (i != 0 && (i % 32 == 0)) hexa += "\n";
                hexa += Buffer[i].ToString("X2") + " ";
            }
            return hexa;
        }

        public String DisplayHexaValues(byte[] Buffer)
        {
            String hexa = "\n";
            //var Buffer = new byte[fileStream.Length];
            //fileStream.Read(Buffer, 0, (int)fileStream.Length);
            for (int i = 0; i < Buffer.Length; i++)
            {
                if (i != 0 && (i % 4 == 0)) hexa += " | ";
                if (i != 0 && (i % 32 == 0)) hexa += "\n";
                hexa += Buffer[i].ToString("X2") + " "; 
            }
            return hexa;
        }

        public String DisplayHexaValues(Int32[] Buffer)
        {
            String hexa = "\n";
            //var Buffer = new byte[fileStream.Length];
            //fileStream.Read(Buffer, 0, (int)fileStream.Length);
            for (int i = 0; i < Buffer.Length; i++)
            {
                if (i != 0 && (i % 4 == 0)) hexa += " | ";
                if (i != 0 && (i % 32 == 0)) hexa += "\n";
                hexa += Buffer[i].ToString("X8") + " ";
            }
            return hexa;
        }

        public UInt16 GetFibGroupValue(FibBase fb, char val)
        {
            UInt16 ret = 0;
            switch (val)
            { 
                case 'G':
                    ret = (UInt16)((fb.group1 >> 6) & 0x1);
                    break;
            }
            return ret;
        }
    }
}
